-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Slack v2 components #18744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Slack v2 components #18744
Conversation
- List users by real name in dropdowns - List users by real name in action summary - List files using bot token if available - Get files using bot token if available - Show configuration error if bot is not a member of the channel - Add `addToChannel` prop to Send Message actions - Check user's access to channel in message actions - Use RTS API to search messages in Find Message action - List only channels, not DMs, in 'file' actions - List only channels, not DMs, in 'message' triggers - Handle permission error fetching message in New Reaction trigger Context: In Slack's v2 OAuth flow, user access tokens and bot access tokens are issued with separate scopes. User tokens are used to perform actions on behalf of a user, while bot tokens are used for actions performed by the app itself. To send a message to a private channel or DM, the bot must be a member of that channel. Due to permission restrictions, some endpoints require bot tokens for access, such as viewing messages in DMs and listing files. As a result, access to messages and files in DMs is no longer supported.
This allows us to support both existing and new Slack connections created with Slack's v2 OAuth flow, with or without the deprecated perspectival chat:write:user and chat:write:bot scopes: | Slack auth | Token Types | Scopes | as_user Values | |------------|-------------|-----------------------|-----------------| | v1 | user | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write | true | In v2, when the chat:write scope is requested, it replaces chat:write:bot and chat:write:user. Without the chat:write:bot scope, as_user cannot be false; user tokens always post as the user, and bot tokens always post as the bot. In v2, with or without the chat:write:bot scope, we can use the bot token if as_user is false since it will have permission to post as itself. And we MAY pass through as_user if it's true since Slack allows it even when it's superfluous. However, we MUST pass through as_user if it's true AND the user token still has the chat:write:bot scope since otherwise the message will post as the bot user instead of the user. See: https://docs.slack.dev/reference/methods/chat.postMessage/#legacy_as_user
so it is shown in the component form by default so users aren't suprised by the new invite behavior
it's no longer supported for new auths since they will not have the im:history scope
as requested by Slack
Fallback to search.messages API method if missing scopese
Future-proofs Slack components to support proxying requests
add/update logged messages when unable to add app to channel
The following components have new minor versions: - delete-message - find-message - get-file - list-files - reply-to-a-message - send-block-kit-message - send-large-message - send-message - send-message-advanced - send-message-to-channel - send-message-to-user-or-group - update-message - new-keyword-mention - new-message-in-channels - new-user-mention All other Slack components have new patch versions.
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
💡 Enable Vercel Agent with $100 free credit for automated AI reviews |
WalkthroughIntroduces a comprehensive Slack v2 component for Pipedream with 30+ actions (messaging, channel management, user operations, file handling), 8+ event sources (new messages, reactions, mentions), a Slack app integration module with extensive API methods, and common utilities including caching and constants. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Action as Slack Action<br/>(send-message)
participant Common as Common Helper<br/>(send-message)
participant App as Slack v2 App
participant SlackAPI as Slack API
User->>Action: Provide channel, text, blocks
Action->>Common: Build message payload
Common->>Common: Generate blocks if BlockKit mode
Common->>App: Get channel info
App->>SlackAPI: conversationsInfo()
SlackAPI-->>App: channel metadata
Common->>Common: Append "Sent via Pipedream" block
Common->>App: Post/Schedule message
alt Schedule (post_at provided)
App->>SlackAPI: chat.scheduleMessage
else Immediate
App->>SlackAPI: chat.postMessage
end
SlackAPI-->>App: response { ts, channel }
App-->>Common: message metadata
Common-->>Action: Success summary
Action-->>User: Message sent ✓
sequenceDiagram
participant Slack as Slack Workspace
participant Webhook as Pipedream<br/>Apphook
participant Source as Event Source<br/>(new-message-in-channels)
participant Cache as Base (Caching)
participant Emit as Emit Event
Slack->>Webhook: Message posted event
Webhook->>Source: processEvent()
Source->>Source: Filter: type == 'message'?
Source->>Source: Filter: allowed subtype?
Source->>Source: Filter: ignoreBot?
Source->>Source: Filter: ignoreThreads?
alt resolveNames enabled
Source->>Cache: getUserName(user_id)
Cache->>Cache: Check cache (ttl: 1hr, size: 1000)
alt Cached
Cache-->>Source: cached name
else Not cached
Cache->>SlackAPI: users.info()
SlackAPI-->>Cache: user profile
Cache->>Cache: Store in cache
Cache-->>Source: name
end
Source->>Source: Enrich event with names
end
Source->>Emit: Event ID + summary + ts
Emit-->>User: Trigger workflow ✓
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Rationale: Despite 50+ new files, most actions follow a consistent, repetitive template (metadata, props, single API call, summary export) requiring quick pattern-based review. The slack_v2.app.mjs file is large with many methods (~40) but uses a straightforward delegation pattern to WebClient. The base.mjs caching layer, send-message utilities, and event source logic introduce moderate complexity with conditional rendering and state management. File spread is wide but interdependencies are minimal. Homogeneous pattern dominance reduces effective review scope. Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
components/slack/actions/send-large-message/send-large-message.mjs (1)
49-64
: Fix metadata_event_payload handling to support object inputs.When
metadata_event_payload
is provided as an object (not a string),metadataEventPayload
remains undefined, resulting inevent_payload: undefined
in the metadata object. The code only assignsmetadataEventPayload
when parsing a string.Apply this diff to handle both string and object inputs:
- let metadataEventPayload; + let metadataEventPayload = this.metadata_event_payload; if (this.metadata_event_type) { if (typeof this.metadata_event_payload === "string") { try { metadataEventPayload = JSON.parse(this.metadata_event_payload); } catch (error) { throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); } } this.metadata = { event_type: this.metadata_event_type, event_payload: metadataEventPayload, }; }components/slack/actions/common/send-message.mjs (1)
213-229
: Fix metadata_event_payload handling to support object inputs.When
metadata_event_payload
is provided as an object (not a string),metadataEventPayload
remains undefined, resulting inevent_payload: undefined
in the metadata object. The code only assignsmetadataEventPayload
when parsing a string.Apply this diff to handle both string and object inputs:
- let metadataEventPayload; + let metadataEventPayload = this.metadata_event_payload; if (this.metadata_event_type) { if (typeof this.metadata_event_payload === "string") { try { metadataEventPayload = JSON.parse(this.metadata_event_payload); } catch (error) { throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); } } this.metadata = { event_type: this.metadata_event_type, event_payload: metadataEventPayload, }; }
🧹 Nitpick comments (1)
components/slack/actions/update-group-members/update-group-members.mjs (1)
54-55
: Good defensive fix!Adding default empty arrays prevents
TypeError
when the optional arrays are undefined, since line 62 calls.includes()
and line 63 spreads the array.Optionally, if a user appears in both the existing group members and
usersToAdd
, they may be duplicated in the final array (line 63). Consider verifying whether the Slack API deduplicates on their end, or add explicit deduplication if needed:users = users.filter((user) => !usersToRemove.includes(user)); - users.push(...usersToAdd); + const allUsers = [...new Set([...users, ...usersToAdd])]; const response = await this.slack.updateGroupMembers({ usergroup: userGroup, - users, + users: allUsers, team_id: team, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (51)
components/azure_cosmos_db/azure_cosmos_db.app.mjs
(1 hunks)components/binalyze_air/binalyze_air.app.mjs
(1 hunks)components/brainbase_labs/brainbase_labs.app.mjs
(1 hunks)components/google_perspective/google_perspective.app.mjs
(1 hunks)components/n1n/n1n.app.mjs
(1 hunks)components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs
(1 hunks)components/slack/actions/approve-workflow/approve-workflow.mjs
(1 hunks)components/slack/actions/archive-channel/archive-channel.mjs
(1 hunks)components/slack/actions/common/send-message.mjs
(4 hunks)components/slack/actions/create-channel/create-channel.mjs
(1 hunks)components/slack/actions/create-reminder/create-reminder.mjs
(1 hunks)components/slack/actions/delete-file/delete-file.mjs
(1 hunks)components/slack/actions/delete-message/delete-message.mjs
(1 hunks)components/slack/actions/find-message/find-message.mjs
(3 hunks)components/slack/actions/find-user-by-email/find-user-by-email.mjs
(1 hunks)components/slack/actions/get-file/get-file.mjs
(3 hunks)components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs
(1 hunks)components/slack/actions/kick-user/kick-user.mjs
(1 hunks)components/slack/actions/list-channels/list-channels.mjs
(1 hunks)components/slack/actions/list-files/list-files.mjs
(3 hunks)components/slack/actions/list-group-members/list-group-members.mjs
(1 hunks)components/slack/actions/list-members-in-channel/list-members-in-channel.mjs
(1 hunks)components/slack/actions/list-replies/list-replies.mjs
(2 hunks)components/slack/actions/list-users/list-users.mjs
(1 hunks)components/slack/actions/reply-to-a-message/reply-to-a-message.mjs
(1 hunks)components/slack/actions/send-block-kit-message/send-block-kit-message.mjs
(1 hunks)components/slack/actions/send-large-message/send-large-message.mjs
(3 hunks)components/slack/actions/send-message-advanced/send-message-advanced.mjs
(1 hunks)components/slack/actions/send-message-to-channel/send-message-to-channel.mjs
(1 hunks)components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs
(2 hunks)components/slack/actions/send-message/send-message.mjs
(1 hunks)components/slack/actions/set-channel-description/set-channel-description.mjs
(1 hunks)components/slack/actions/set-channel-topic/set-channel-topic.mjs
(1 hunks)components/slack/actions/set-status/set-status.mjs
(1 hunks)components/slack/actions/update-group-members/update-group-members.mjs
(2 hunks)components/slack/actions/update-message/update-message.mjs
(1 hunks)components/slack/actions/update-profile/update-profile.mjs
(1 hunks)components/slack/actions/upload-file/upload-file.mjs
(1 hunks)components/slack/actions/verify-slack-signature/verify-slack-signature.mjs
(0 hunks)components/slack/package.json
(1 hunks)components/slack/slack.app.mjs
(9 hunks)components/slack/sources/new-channel-created/new-channel-created.mjs
(1 hunks)components/slack/sources/new-direct-message/new-direct-message.mjs
(0 hunks)components/slack/sources/new-direct-message/test-event.mjs
(0 hunks)components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs
(1 hunks)components/slack/sources/new-keyword-mention/new-keyword-mention.mjs
(2 hunks)components/slack/sources/new-message-in-channels/new-message-in-channels.mjs
(2 hunks)components/slack/sources/new-reaction-added/new-reaction-added.mjs
(2 hunks)components/slack/sources/new-saved-message/new-saved-message.mjs
(1 hunks)components/slack/sources/new-user-added/new-user-added.mjs
(1 hunks)components/slack/sources/new-user-mention/new-user-mention.mjs
(2 hunks)
💤 Files with no reviewable changes (3)
- components/slack/sources/new-direct-message/new-direct-message.mjs
- components/slack/actions/verify-slack-signature/verify-slack-signature.mjs
- components/slack/sources/new-direct-message/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (3)
components/slack/actions/common/send-message.mjs (2)
components/slack/slack.app.mjs (3)
channelId
(24-27)channel
(505-507)channel
(514-516)components/slack/actions/send-large-message/send-large-message.mjs (1)
channelName
(91-91)
components/slack/actions/send-large-message/send-large-message.mjs (1)
components/slack/slack.app.mjs (2)
channel
(505-507)channel
(514-516)
components/slack/slack.app.mjs (2)
components/slack/actions/get-file/get-file.mjs (1)
response
(53-55)components/slack/actions/find-message/find-message.mjs (3)
response
(115-119)response
(134-138)cursor
(112-112)
🔇 Additional comments (29)
components/slack/sources/new-channel-created/new-channel-created.mjs (1)
8-8
: Version bump looks good.No issues spotted with the update to version 0.0.11.
components/slack/actions/archive-channel/archive-channel.mjs (1)
8-8
: Version bump looks good.Version increment to 0.0.25 is consistent and introduces no regressions.
components/slack/actions/set-channel-topic/set-channel-topic.mjs (1)
7-7
: Version bump looks good.Matches surrounding Slack action updates; no further changes needed.
components/slack/actions/update-profile/update-profile.mjs (1)
8-8
: Version bump looks good.Increment to 0.0.25 keeps this action aligned with the other Slack updates in the PR. No further action needed.
components/slack/actions/approve-workflow/approve-workflow.mjs (1)
8-8
: Version bump looks good.Matches the broader Slack release versioning pattern with no functional impact.
components/slack/sources/new-user-added/new-user-added.mjs (1)
8-8
: Version bump looks goodThe source metadata increment is consistent and introduces no risk.
components/slack/actions/create-reminder/create-reminder.mjs (1)
7-7
: Version bump looks goodThe metadata-only bump to 0.0.26 keeps this action aligned with the broader Slack release. 👍
components/slack/actions/send-block-kit-message/send-block-kit-message.mjs (1)
10-10
: Version bump looks good.Increment aligns with the broader Slack v2 rollout; no further action needed.
components/slack/actions/kick-user/kick-user.mjs (1)
8-8
: Version bump looks consistent.Matches the broader component release cadence. No further issues spotted.
components/azure_cosmos_db/azure_cosmos_db.app.mjs (1)
11-11
: LGTM — object terminator restored.Thanks for reintroducing the closing
};
; keeps the module syntax tidy.components/slack/actions/reply-to-a-message/reply-to-a-message.mjs (1)
9-9
: Version bump looks goodIncrement to
0.2.0
is consistent with the ongoing Slack updates in this PR.components/slack/sources/new-saved-message/new-saved-message.mjs (1)
8-8
: Version bump looks good.The patch version increment is appropriate for this maintenance update.
Note: Line 9 mentions this endpoint is deprecated by Slack and may be shut down in the future. Consider tracking this deprecation to ensure timely migration or removal when Slack discontinues support.
components/slack/actions/find-user-by-email/find-user-by-email.mjs (1)
7-7
: LGTM! Version bump aligns with OAuth v2 compatibility.The version increment appropriately marks this action as compatible with the underlying Slack OAuth v2 changes, even though no logic changes were required.
components/slack/actions/list-channels/list-channels.mjs (1)
7-7
: Verify that no functional changes are needed for v2 OAuth support.The version bump appears intentional for indicating v2 OAuth compatibility. Since this is a read-only action that only lists channels (as indicated by the
readOnlyHint: true
annotation), it likely doesn't require the additional props (likeaddToChannel
) or pre-run logic that other Slack actions in this PR received.However, please confirm that the underlying
slack.app.mjs
component handles all necessary v2 OAuth changes transparently, and that this action will function correctly without any code modifications.components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
7-7
: LGTM: Version bump for coordinated release.The metadata-only version bump aligns with the coordinated release pattern across Slack actions in this PR.
components/slack/actions/update-group-members/update-group-members.mjs (1)
7-7
: LGTM!Version bump is appropriate for this defensive code improvement.
components/slack/actions/list-replies/list-replies.mjs (3)
1-1
: LGTM!The constants import is correctly added and used for channel type constraints.
8-8
: LGTM!The version bump from 0.0.24 to 0.0.25 is appropriate for this change.
21-28
: Confirm DM/group channel support isn't required: list-replies now limits the picker to CHANNEL_TYPE.PUBLIC and CHANNEL_TYPE.PRIVATE—verify no existing workflows depend on CHANNEL_TYPE.IM or CHANNEL_TYPE.MPIM.components/slack/sources/new-reaction-added/new-reaction-added.mjs (2)
8-8
: LGTM: Version bump aligns with error handling changes.The minor version increment from
1.1.26
to1.2.0
appropriately reflects the defensive error handling added toprocessEvent
.
100-107
: Approve with observability enhancements
- Replace console.log with a structured logger or emit a metric on getMessage failure.
- Downstream Slack handlers do not reference event.message, so undefined messages won’t break processing.
- If message retrieval is critical, attach an error field to the returned event for better troubleshooting.
components/slack/actions/send-large-message/send-large-message.mjs (2)
38-42
: LGTM! Pre-send channel invitation logic is correct.The conditional check for
addToChannel
and subsequent call tomaybeAddAppToChannels
ensures the app is properly invited to the channel before sending the message. TheaddToChannel
prop is correctly inherited from the common send-message module.
91-91
: LGTM! Channel name resolution refactored to use helper.Using
getChannelDisplayName
centralizes the channel display logic and improves maintainability.components/slack/sources/new-user-mention/new-user-mention.mjs (1)
20-25
: LGTM! Channel type constraints correctly applied.The dynamic type constraints limit conversation selection to public and private channels using shared constants, aligning with the pattern adopted across other Slack sources and actions in this PR.
components/slack/actions/common/send-message.mjs (2)
12-17
: LGTM! addToChannel prop and pre-send logic implemented correctly.The new
addToChannel
prop allows users to opt into automatic channel invitation, and the pre-send logic efficiently cacheschannelId
and invokesmaybeAddAppToChannels
when needed.Also applies to: 190-196
258-258
: LGTM! Channel name resolution refactored to use helper.Using
getChannelDisplayName
centralizes the channel display logic and improves maintainability.components/slack/sources/new-keyword-mention/new-keyword-mention.mjs (1)
20-25
: LGTM! Channel type constraints correctly applied.The dynamic type constraints limit conversation selection to public and private channels using shared constants, consistent with the pattern in new-user-mention.mjs and other Slack components.
components/slack/actions/get-file/get-file.mjs (1)
21-29
: LGTM! All changes follow the established pattern.The channel type constraints,
addToChannel
prop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.Also applies to: 30-35, 47-51
components/slack/actions/list-files/list-files.mjs (1)
21-29
: LGTM! All changes follow the established pattern.The channel type constraints,
addToChannel
prop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.Also applies to: 30-35, 64-68
components/slack/slack.app.mjs
Outdated
if ([ | ||
"not_in_channel", | ||
"channel_not_found", | ||
].some((errorType) => error.includes(errorType)) && asBot) { | ||
const followUp = method.startsWith("chat.") | ||
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | ||
: "Ensure the bot is a member of the channel."; | ||
throw new ConfigurationError(`${error}\n${followUp}`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix error handling before it throws a TypeError
error
is typically an Error
object (e.g., WebAPIPlatformError
), so calling error.includes(...)
throws TypeError: error.includes is not a function
, hiding the original Slack error instead of surfacing it. Coerce the error to a string (or read error.data?.error
) before running .includes
, and use that value in the ConfigurationError
message.
- if ([
- "not_in_channel",
- "channel_not_found",
- ].some((errorType) => error.includes(errorType)) && asBot) {
- const followUp = method.startsWith("chat.")
- ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
- : "Ensure the bot is a member of the channel.";
- throw new ConfigurationError(`${error}\n${followUp}`);
- }
- throw error;
+ const errorMessage = typeof error === "string"
+ ? error
+ : error?.data?.error || error.message || `${error}`;
+ if ([
+ "not_in_channel",
+ "channel_not_found",
+ ].some((errorType) => errorMessage.includes(errorType)) && asBot) {
+ const followUp = method.startsWith("chat.")
+ ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
+ : "Ensure the bot is a member of the channel.";
+ throw new ConfigurationError(`${errorMessage}\n${followUp}`);
+ }
+ throw error;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if ([ | |
"not_in_channel", | |
"channel_not_found", | |
].some((errorType) => error.includes(errorType)) && asBot) { | |
const followUp = method.startsWith("chat.") | |
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | |
: "Ensure the bot is a member of the channel."; | |
throw new ConfigurationError(`${error}\n${followUp}`); | |
} | |
const errorMessage = typeof error === "string" | |
? error | |
: error?.data?.error || error.message || `${error}`; | |
if ([ | |
"not_in_channel", | |
"channel_not_found", | |
].some((errorType) => errorMessage.includes(errorType)) && asBot) { | |
const followUp = method.startsWith("chat.") | |
? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | |
: "Ensure the bot is a member of the channel."; | |
throw new ConfigurationError(`${errorMessage}\n${followUp}`); | |
} | |
throw error; |
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 561 to 569, the code assumes
`error` is a string and calls `error.includes(...)`, which throws a TypeError
when `error` is an Error object; change the logic to extract a safe string first
(e.g., const errMsg = typeof error === "string" ? error : error?.data?.error ??
String(error)), use `errMsg.includes(...)` for the conditional, and use `errMsg`
when constructing the ConfigurationError so the original Slack error is
preserved and no TypeError is thrown.
guard against unexpected types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/slack/actions/find-message/find-message.mjs (1)
42-42
: Fix stray backtick in description.Clean up the string.
- description: "Sort ascending (asc) or descending (desc)`", + description: "Sort ascending (asc) or descending (desc)",
♻️ Duplicate comments (1)
components/slack/slack.app.mjs (1)
561-569
: Implement the error message extraction suggested in the previous review.While
${error}
prevents the TypeError, the previous review correctly noted that extractingerror.data?.error
would preserve the original Slack error message more accurately. The current string coercion might lose structured error details.Apply the diff suggested in the previous review to properly extract and preserve error details.
🧹 Nitpick comments (7)
components/slack/slack.app.mjs (1)
502-527
: Consider logging caught errors for debugging.While the fallback strings provide good UX when name resolution fails, silently catching errors at lines 509 and 522 could hide legitimate issues (e.g., auth failures, rate limits). Consider adding
console.error(err)
or similar logging in the catch blocks to aid troubleshooting.components/slack/actions/find-message/find-message.mjs (6)
80-90
: Normalize merge precedence to avoid accidental overrides.Currently, ...baseMessage and ...rest can override canonical fields (user, ts, text, type, channel). If the canonical fields should win, flip the spread order.
- const normalized = { - type: "message", - user: authorUserId, - team: teamId, - ts: messageTs, - text: content, - permalink, - channel: channelInfo, - ...baseMessage, - ...rest, - }; + const normalized = { + ...baseMessage, + ...rest, + type: "message", + user: authorUserId, + team: teamId, + ts: messageTs, + text: content, + permalink, + channel: channelInfo, + };If overriding by baseMessage/rest is intentional, consider adding a brief comment to document the precedence.
115-119
: Align with existing Slack client usage: pass throwRateLimitError.Other Slack app calls set throwRateLimitError: true. Consider adding it here for consistency and clearer retry behavior. Based on relevant code snippets.
- const response = await this.slack.assistantSearch({ + const response = await this.slack.assistantSearch({ ...baseParams, channel_types: "public_channel,private_channel", cursor, + throwRateLimitError: true, });
117-118
: Channel types exclude DMs — intentional?assistantSearch is limited to public/private channels. If you also want to match IM/MPIM messages, add im,mpim or make it configurable.
- channel_types: "public_channel,private_channel", + channel_types: "public_channel,private_channel,im,mpim",
134-138
: Also pass throwRateLimitError to searchMessages.Match the pattern used across the Slack app for consistent error semantics. Based on relevant code snippets.
- const response = await this.slack.searchMessages({ + const response = await this.slack.searchMessages({ ...baseParams, count, page, + throwRateLimitError: true, });
190-194
: Avoid sending undefined params to the API.Only include sort/sort_dir when set to reduce risk of invalid query params.
- const baseParams = { - query: this.query, - sort: this.sort, - sort_dir: this.sortDirection, - }; + const baseParams = { + query: this.query, + ...(this.sort && { sort: this.sort }), + ...(this.sortDirection && { sort_dir: this.sortDirection }), + };
197-205
: Optional: Fallback if assistantSearch is unavailable.If older app versions or tokens don’t expose assistantSearch, pre-check and use searchMessages directly.
- try { - matches = await this.searchWithAssistant(baseParams, maxResults); - } catch (error) { + try { + if (typeof this.slack.assistantSearch !== "function") { + matches = await this.searchWithSearchMessages(baseParams, maxResults); + } else { + matches = await this.searchWithAssistant(baseParams, maxResults); + } + } catch (error) { if (this.shouldFallbackToSearchMessages(error)) { matches = await this.searchWithSearchMessages(baseParams, maxResults); } else { throw error; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
components/slack/actions/find-message/find-message.mjs
(3 hunks)components/slack/slack.app.mjs
(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/slack/actions/find-message/find-message.mjs (1)
components/slack/slack.app.mjs (8)
channel
(505-507)channel
(514-516)cursor
(187-196)cursor
(240-247)cursor
(629-629)cursor
(653-653)response
(557-557)page
(293-298)
components/slack/slack.app.mjs (2)
components/slack/actions/find-message/find-message.mjs (3)
response
(115-119)response
(134-138)cursor
(112-112)components/slack/actions/get-file/get-file.mjs (1)
response
(53-55)
🔇 Additional comments (9)
components/slack/slack.app.mjs (7)
5-5
: LGTM: ConfigurationError import supports better error handling.The import enables throwing user-friendly configuration errors when bot permissions are insufficient, improving the developer experience.
31-32
: LGTM: Name resolution improvements are well-implemented.The changes correctly:
- Filter out falsy user IDs before lookup
- Optimize by resolving IDs and usernames in parallel
- Extract usernames from MPIM purpose values using regex
The inline comment at lines 140-142 clearly explains the optimization rationale.
Also applies to: 124-143
468-473
: LGTM: addToChannel prop provides good user control.The prop definition is clear, with a sensible default that automates the common case while allowing users to opt out when needed.
491-501
: LGTM: Token selection logic is clear and correct.The methods properly implement the bot token precedence: use bot token when explicitly requested via
asBot
and available, otherwise fall back to user token.
532-536
: LGTM: SDK initialization properly supports token options.The method correctly passes options to
getToken
for bot vs. user token selection and includesslackApiUrl
configuration for custom Slack instances.
679-709
: LGTM: Robust error handling for channel invitations.The method correctly:
- Checks for bot token availability
- Validates bot_id before proceeding
- Handles per-channel errors gracefully with logging
- Skips DM/group DM channels automatically (per the comment)
Note: Sequential channel processing (lines 694-705) could be slow for many channels, but this is likely necessary given Slack's API constraints.
948-973
: LGTM: File methods correctly use bot token with clear documentation.The comments at lines 952-954 and 968-970 clearly explain that the bot token is required because the
files:read
scope is only requested for bot tokens in the Pipedream app. This prevents auth errors when using file operations.components/slack/actions/find-message/find-message.mjs (2)
6-7
: Verify the new docs endpoint and scope expectations.Confirm that assistant.search.context is the intended API reference for this action and that required scopes match the implementation and fallback logic.
163-170
: Good fix: guarded includes on errorCode.The optional chaining prevents TypeError when errorCode is falsy. This resolves the earlier review concern.
components/slack/slack.app.mjs
Outdated
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | ||
let cursor; | ||
const realNames = {}; | ||
do { | ||
const { | ||
members: users, | ||
response_metadata: { next_cursor: nextCursor }, | ||
} = await this.usersList({ | ||
limit: constants.LIMIT, | ||
cursor, | ||
throwRateLimitError, | ||
...args, | ||
}); | ||
|
||
for (const user of users) { | ||
if (ids.includes(user.id)) { | ||
realNames[user.id] = user.profile.real_name; | ||
} | ||
if (usernames.includes(user.name)) { | ||
realNames[user.name] = user.profile.real_name; | ||
} | ||
} | ||
|
||
cursor = nextCursor; | ||
} while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); | ||
return realNames; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential infinite loop and performance issue.
Two issues in realNameLookup
:
-
Infinite loop risk (line 676): If some IDs or usernames don't exist in the workspace,
realNames
will never reach the expected count, causing the loop to paginate indefinitely. -
O(n²) performance (lines 667, 670): Using
includes()
on arrays inside a loop is inefficient for large lists.
Apply this diff to fix both issues:
- async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+ async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+ const idSet = new Set(ids);
+ const usernameSet = new Set(usernames);
let cursor;
const realNames = {};
+ const targetCount = ids.length + usernames.length;
do {
const {
members: users,
response_metadata: { next_cursor: nextCursor },
} = await this.usersList({
limit: constants.LIMIT,
cursor,
throwRateLimitError,
...args,
});
for (const user of users) {
- if (ids.includes(user.id)) {
+ if (idSet.has(user.id)) {
realNames[user.id] = user.profile.real_name;
}
- if (usernames.includes(user.name)) {
+ if (usernameSet.has(user.name)) {
realNames[user.name] = user.profile.real_name;
}
}
cursor = nextCursor;
- } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length));
+ } while (cursor && Object.keys(realNames).length < targetCount);
return realNames;
},
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | |
let cursor; | |
const realNames = {}; | |
do { | |
const { | |
members: users, | |
response_metadata: { next_cursor: nextCursor }, | |
} = await this.usersList({ | |
limit: constants.LIMIT, | |
cursor, | |
throwRateLimitError, | |
...args, | |
}); | |
for (const user of users) { | |
if (ids.includes(user.id)) { | |
realNames[user.id] = user.profile.real_name; | |
} | |
if (usernames.includes(user.name)) { | |
realNames[user.name] = user.profile.real_name; | |
} | |
} | |
cursor = nextCursor; | |
} while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); | |
return realNames; | |
}, | |
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | |
const idSet = new Set(ids); | |
const usernameSet = new Set(usernames); | |
let cursor; | |
const realNames = {}; | |
const targetCount = ids.length + usernames.length; | |
do { | |
const { | |
members: users, | |
response_metadata: { next_cursor: nextCursor }, | |
} = await this.usersList({ | |
limit: constants.LIMIT, | |
cursor, | |
throwRateLimitError, | |
...args, | |
}); | |
for (const user of users) { | |
if (idSet.has(user.id)) { | |
realNames[user.id] = user.profile.real_name; | |
} | |
if (usernameSet.has(user.name)) { | |
realNames[user.name] = user.profile.real_name; | |
} | |
} | |
cursor = nextCursor; | |
} while (cursor && Object.keys(realNames).length < targetCount); | |
return realNames; | |
}, |
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 652 to 678, fix the potential
infinite loop and O(n²) performance by converting ids and usernames to Sets
before pagination, then on each page iterate members once and check membership
against those Sets (removing found entries from the Sets and mapping id/username
to real_name), and stop looping when either the Sets are empty (all requested
items resolved) or there is no next_cursor from usersList; also defensively
handle missing response_metadata/next_cursor to avoid relying on undefined and
thus prevent infinite pagination.
components/slack/slack.app.mjs
Outdated
assistantSearch(args = {}) { | ||
args.count ||= constants.LIMIT; | ||
return this.sdk().apiCall("assistant.search.context", { | ||
...args, | ||
}); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify whether assistantSearch should use makeRequest for consistency.
Unlike other methods, assistantSearch
calls sdk().apiCall()
directly, bypassing the retry logic and rate limit handling provided by makeRequest
and _withRetries
. This could lead to inconsistent behavior compared to other Slack API calls.
If the assistant.search.context
API has different requirements that necessitate direct SDK usage, please document this with a comment. Otherwise, consider refactoring to use makeRequest
for consistency.
🏁 Script executed:
#!/bin/bash
# Description: Check if other methods use direct apiCall instead of makeRequest
# Search for other instances of direct apiCall usage
rg -n "sdk\(\)\.apiCall" components/slack/
Length of output: 96
🏁 Script executed:
#!/bin/bash
rg -n "this\\.makeRequest" components/slack/slack.app.mjs -C3
Length of output: 7042
🏁 Script executed:
#!/bin/bash
# Search for makeRequest method definition
rg -n "makeRequest\s*\(" components/slack/slack.app.mjs -A10 | head -30
Length of output: 1074
🏁 Script executed:
#!/bin/bash
# Search for _withRetries within makeRequest method
rg -n "_withRetries" components/slack/slack.app.mjs -C2 | head -40
Length of output: 333
Refactor assistantSearch
to use makeRequest
for consistency with all other API methods.
The method uses direct sdk().apiCall()
instead of makeRequest
, bypassing the retry mechanism (3 retries) and rate-limit handling provided by _withRetries
that every other API method in this file uses. This creates inconsistent error handling and resilience across the codebase.
At lines 895–900, change:
return this.sdk().apiCall("assistant.search.context", {
...args,
});
To:
return this.makeRequest({
method: "assistant.search.context",
...args,
});
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 895 to 900, assistantSearch
currently calls this.sdk().apiCall directly (bypassing retries/rate-limit) and
should be changed to use this.makeRequest to get the same retry and rate-limit
behavior; set args.count default as before, then return this.makeRequest with
method: "assistant.search.context" and spread the rest of args so the call
signature and returned value remain identical but now uses the shared retry
wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @slack/web-api
npm package doesn't currently expose a method on WebClient
for this API method, so we can't call it like the other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@js07, thank you for the clarification! That makes perfect sense—if the @slack/web-api
package doesn't expose assistant.search.context
as a method on WebClient
, then using apiCall()
directly is the correct approach.
To help future maintainers understand why this method differs from the others, consider adding a brief comment above the method:
// Uses apiCall directly since assistant.search.context is not exposed as a method on WebClient
assistantSearch(args = {}) {
args.count ||= constants.LIMIT;
return this.sdk().apiCall("assistant.search.context", {
...args,
});
}
This will make it clear that the direct apiCall()
usage is intentional due to SDK limitations.
✏️ Learnings added
Learnt from: js07
PR: PipedreamHQ/pipedream#18744
File: components/slack/slack.app.mjs:0-0
Timestamp: 2025-10-20T00:28:38.702Z
Learning: In components/slack/slack.app.mjs, the assistantSearch method uses sdk().apiCall() directly instead of makeRequest because the slack/web-api npm package doesn't expose assistant.search.context as a method on WebClient. This direct usage is intentional and necessary when Slack API methods are not yet exposed in the SDK.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Copies the action with the same name added to the Slack app in the master branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 34
🧹 Nitpick comments (36)
components/slack_v2/sources/new-interaction-event-received/README.md (1)
81-85
: Vary sentence structure in troubleshooting steps to improve readability.Three consecutive list items all begin with "Make sure that", which creates repetitive reading. Vary the phrasing for better flow.
1. Make sure that your `action_id` or ****************channels**************** filters apply to that message, remove the filters to make sure that's not the case. -1. Make sure that the message comes from the same Slack account that this source is configured with. +1. Verify the message comes from the same Slack account that this source is configured with. -1. Make sure that the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app. +1. Confirm the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app.components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (2)
24-41
: Define label/description forslackApphook
; remove eslint disable.Improve clarity and satisfy lint without disabling rules.
- // eslint-disable-next-line pipedream/props-description,pipedream/props-label slackApphook: { type: "$.interface.apphook", appProp: "slack", + label: "Slack Apphook", + description: "Internal apphook used to receive Slack events. Do not modify.", async eventNames() {
71-79
: Simplify emoji parsing;iconEmoji
is already a string[] prop.Current JSON/CSV parsing is unnecessary and fragile. Prefer normalized array handling.
- let iconEmojiParsed = []; - - try { - iconEmojiParsed = typeof this.iconEmoji === "string" ? - JSON.parse(this.iconEmoji) : - this.iconEmoji; - } catch (error) { - iconEmojiParsed = this.iconEmoji.replace(/\s+/g, "").split(","); - } + const iconEmojiParsed = Array.isArray(this.iconEmoji) + ? this.iconEmoji.map((e) => e.trim()).filter(Boolean) + : [];components/slack_v2/package.json (1)
1-22
: Add license/engines and pin critical deps for reproducible builds.
- Include a license field and Node engines (Pipedream runs Node 18+).
- Consider pinning to exact versions for stability, especially
@pipedream/platform
(recommended to pin 3.1.0). Based on learnings.{ "name": "@pipedream/slack_v2", "version": "0.0.1", "description": "Pipedream Slack_v2 Components", "main": "slack_v2.app.mjs", + "license": "MIT", + "engines": { "node": ">=18" }, "keywords": [ @@ "dependencies": { - "@pipedream/platform": "^3.1.0", - "@slack/web-api": "^7.9.0", + "@pipedream/platform": "3.1.0", + "@slack/web-api": "^7.9.0", "async-retry": "^1.3.3", "lodash": "^4.17.21" } }Optionally add
"repository"
metadata.
To confirm latest safe@slack/web-api
compatible with Node 18, please verify current releases and changelog.components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs (1)
23-31
: LGTM; consider clearer handling when user isn’t found.Slack returns
ok: false
withusers_not_found
. Optionally set a friendly summary or throw when not found.const response = await this.slack.lookupUserByEmail({ email: this.email, }); - if (response.ok) { + if (response.ok) { $.export("$summary", `Successfully found user with ID ${response.user.id}`); - } + } else if (response.error === "users_not_found") { + $.export("$summary", "No user found for the provided email"); + } return response;components/slack_v2/common/constants.mjs (1)
11-24
: Inconsistent value mapping in CHANNEL_TYPE_OPTIONS.The first option uses
"Channels"
as its value instead of aCHANNEL_TYPE
constant. This breaks the pattern established by the other two options (which useCHANNEL_TYPE.MPIM
andCHANNEL_TYPE.IM
). While this appears intentional (based on usage inapprove-workflow.mjs
where"Channels"
maps to both PUBLIC and PRIVATE channel types), it creates an undocumented special case that could confuse maintainers.Consider either:
- Adding a comment explaining that
"Channels"
is a special value representing both PUBLIC and PRIVATE channels, or- Refactoring to use a more explicit constant like
CHANNEL_TYPE.PUBLIC_AND_PRIVATE
or an array value for consistency.components/slack_v2/actions/set-status/set-status.mjs (1)
37-48
: Consider omitting optional properties when not provided.Lines 41 and 42-43 use the
&&
operator to conditionally setstatus_emoji
andstatus_expiration
. When these optional props are not provided, the properties will be set to falsy values (e.g.,undefined
,false
,0
) rather than being omitted from the object. Some APIs may not handle falsy values gracefully.Consider using the spread operator to omit properties entirely when not provided:
const response = await this.slack.updateProfile({ profile: { status_text: this.statusText, - status_emoji: this.statusEmoji && `:${this.statusEmoji}:`, - status_expiration: this.statusExpiration - && Math.floor(new Date(this.statusExpiration).getTime() / 1000), + ...(this.statusEmoji && { status_emoji: `:${this.statusEmoji}:` }), + ...(this.statusExpiration && { + status_expiration: Math.floor(new Date(this.statusExpiration).getTime() / 1000), + }), }, });components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
61-65
: Optional: Remove unnecessary optional chaining.Since
members
is initialized as an empty array on line 44, it can never benull
orundefined
, making the optional chaining on line 61 unnecessary.- if (members?.length) { + if (members.length) {components/slack_v2/actions/update-message/update-message.mjs (1)
48-58
: LGTM! Consider enhancing the summary with context.The implementation correctly maps parameters and calls the Slack API. The generic success message is acceptable, though including the channel or timestamp could make debugging easier.
Optional enhancement:
- $.export("$summary", "Successfully updated message"); + $.export("$summary", `Successfully updated message in channel ${this.conversation}`);components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs (1)
24-34
: Make channel multi-select via propDefinition options, not parallel type fieldsDeclare multi-select in the propDefinition to avoid schema drift between
type: "string[]"
and the underlying definition.Apply:
- conversations: { - propDefinition: [ - common.props.slack, - "conversation", - ], - type: "string[]", - label: "Channels", - description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.", - optional: true, - default: [], - }, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ multiple: true }), + ], + label: "Channels", + description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.", + optional: true, + },components/slack_v2/actions/upload-file/upload-file.mjs (2)
52-76
: Use metadata-derived filename consistentlyUse
metadata.name
for both the presign request and the form field to avoid mismatches with path parsing.Apply:
- const filename = this.content.split("/").pop(); + const filename = metadata.name; ... - formData.append("filename", filename); + formData.append("filename", filename);
98-104
: Consistent error type + more informative summaryUse the same error style as presign and include filename/channel in the summary.
Apply:
- if (!completeUploadResponse.ok) { - throw new Error(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`); - } + if (!completeUploadResponse?.ok) { + throw new ConfigurationError(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`); + } - - $.export("$summary", "Successfully uploaded file"); - return completeUploadResponse; + $.export("$summary", `Successfully uploaded ${metadata.name} to ${this.conversation}`); + return completeUploadResponse;components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
17-33
: Consider removing redundant slack prop definition.Line 18 redefines
slack: common.props.slack
, but line 31 already spreads...common.props
which includes the slack prop. While this doesn't cause an error (the spread overwrites the earlier definition), it creates unnecessary redundancy.Apply this diff to remove the redundancy:
props: { - slack: common.props.slack, conversation: { propDefinition: [ common.props.slack, "conversation", ], }, text: { type: "string", label: "Notification Text", description: "Optionally provide a string for Slack to display as the new message notification (if you do not provide this, notification will be blank).", optional: true, }, ...common.props, ...buildBlocks.props, },
components/slack_v2/actions/list-files/list-files.mjs (1)
80-85
: Consider using a boolean for hasMore.Line 83 assigns
hasMore = files.length
, making it a number rather than a boolean. While this works in the loop condition, it reduces code clarity. Consider usinghasMore = files.length > 0
orhasMore = files.length === this.pageSize
for clearer intent.Apply this diff to improve clarity:
do { const { files } = await this.slack.listFiles(params); allFiles.push(...files); - hasMore = files.length; + hasMore = files.length > 0; params.page++; } while (hasMore && params.page <= this.numPages);components/slack_v2/sources/new-saved-message/new-saved-message.mjs (1)
25-30
: Improve summary for observabilityConsider including user and channel in the summary for quick triage, e.g., “ saved a message in ”.
- getSummary() { - return "New saved message"; - }, + getSummary(event) { + const user = event?.user || "Someone"; + const channel = event?.item?.channel || "a channel"; + return `${user} saved a message in ${channel}`; + },components/slack_v2/actions/update-group-members/update-group-members.mjs (1)
69-70
: Skip no-op updates, and align summary variableAvoid the API call when membership is unchanged; also use the local
userGroup
for consistency.- $.export("$summary", `Successfully updated members of group with ID ${this.userGroup}`); - return response; + if (response?.ok === false) return response; + $.export("$summary", `Updated members of group ${userGroup} (${users.length} users)`); + return response;components/slack_v2/actions/find-message/find-message.mjs (1)
51-66
: Guard against cursor loops and overfetchAdd a seen‑cursor set to avoid infinite loops if an API bug returns the same cursor; stop when reaching maxResults.
async searchWithAssistant(baseParams, maxResults) { const matches = []; - let cursor; + let cursor; + const seen = new Set(); do { const response = await this.slack.assistantSearch({ ...baseParams, channel_types: "public_channel,private_channel", cursor, }); matches.push(...response.results?.messages || []); - cursor = response.response_metadata?.next_cursor; - } while (cursor && matches.length < maxResults); + const next = response.response_metadata?.next_cursor; + if (!next || seen.has(next) || matches.length >= maxResults) break; + seen.add(next); + cursor = next; + } while (true); return matches.slice(0, maxResults); },components/slack_v2/actions/list-users/list-users.mjs (1)
44-49
: Defensive default for missing membersGuard against undefined
members
to avoid pushingundefined
.- users.push(...members); + users.push(...(members ?? []));components/slack_v2/actions/send-large-message/send-large-message.mjs (3)
15-36
: Props spread order causes duplication and overridesYou define
slack
,conversation
,text
,mrkdwn
then spread...common.props
, which overrides them. Either remove duplicates or spread first and override intentionally.- props: { - slack: common.props.slack, - conversation: { propDefinition: [ common.props.slack, "conversation" ] }, - text: { propDefinition: [ common.props.slack, "text" ] }, - mrkdwn: { propDefinition: [ common.props.slack, "mrkdwn" ] }, - ...common.props, - }, + props: { + ...common.props, + // override here only if you need different labels/descriptions/defaults: + // conversation: { ... }, + // text: { ... }, + // mrkdwn: { ... }, + },
44-47
: Optional: safer “sent via Pipedream” text extractionGuard against unexpected block structure when appending the flag text.
- const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); - this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`; + const block = this._makeSentViaPipedreamBlock(); + const text = block?.elements?.[0]?.text; + if (text) this.text += `\n\n\n${text}`;
66-87
: Consider adding message chunking or file upload fallback for optimal Slack deliveryMessages sent via this action can exceed Slack's recommended text length for best results. Slack recommends keeping top-level text to approximately 4,000 characters for readability, and messages longer than 40,000 characters are truncated with a warning returned. Consider chunking
this.text
into multiple messages or implementing a fallback to file upload when the message size exceeds the ~4,000 character threshold.components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs (1)
79-86
: Tighten comparisons and parallelize name resolution.Use strict equality and resolve names concurrently to reduce latency under load.
- if ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) { + if ((this.ignoreBot) && (event.subtype === "bot_message" || event.bot_id)) { return; } @@ - if (this.resolveNames) { - if (event.user) { - event.user_id = event.user; - event.user = await this.getUserName(event.user); - } else if (event.bot_id) { - event.bot = await this.getBotName(event.bot_id); - } - event.channel_id = event.channel; - event.channel = await this.getConversationName(event.channel); - if (event.team) { - event.team_id = event.team; - event.team = await this.getTeamName(event.team); - } - } + if (this.resolveNames) { + const promises = []; + if (event.user) { + event.user_id = event.user; + promises.push(this.getUserName(event.user).then((name) => { event.user = name; })); + } else if (event.bot_id) { + promises.push(this.getBotName(event.bot_id).then((name) => { event.bot = name; })); + } + event.channel_id = event.channel; + promises.push(this.getConversationName(event.channel).then((name) => { event.channel = name; })); + if (event.team) { + event.team_id = event.team; + promises.push(this.getTeamName(event.team).then((name) => { event.team = name; })); + } + await Promise.all(promises); + }Also applies to: 88-101
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
18-19
: Avoid duplicateslack
prop; optionally expose rawblocks
input.
slack
is already provided via...common.props
; defining it again is redundant.- Expose the app’s
blocks
prop so users can paste raw Block Kit JSON when not using the builder.props: { - slack: common.props.slack, + // rely on ...common.props for `slack` conversation: { propDefinition: [ common.props.slack, "conversation", ], }, @@ link_names: { propDefinition: [ common.props.slack, "link_names", ], }, + blocks: { + propDefinition: [ + common.props.slack, + "blocks", + ], + }, ...common.props, ...buildBlocks.props, },Also applies to: 56-58
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)
49-56
: Harden pagination and results (handle missing members, dedupe IDs).Prevents runtime errors if
members
is undefined and avoids duplicate IDs across pages; minor efficiency win before username lookup.do { const { - members, response_metadata: { next_cursor: nextCursor }, + members, response_metadata: { next_cursor: nextCursor }, } = await this.slack.listChannelMembers(params); - channelMembers.push(...members); + channelMembers.push(...(members ?? [])); params.cursor = nextCursor; page++; } while (params.cursor && page < this.numPages); + // Deduplicate before optional username lookup + channelMembers = [...new Set(channelMembers)];Also applies to: 58-64
components/slack_v2/slack_v2.app.mjs (5)
628-651
: Speed up ID lookups with a Set (avoid O(n²)).Current
ids.includes(user.id)
scales poorly on large teams.- let cursor; - const userNames = {}; + let cursor; + const userNames = {}; + const target = new Set(ids); do { @@ - for (const user of users) { - if (ids.includes(user.id)) { - userNames[user.id] = user.name; - } - } + for (const user of users) { + if (target.has(user.id)) userNames[user.id] = user.name; + }
652-678
: Same Set optimization for realNameLookup.- let cursor; - const realNames = {}; + let cursor; + const realNames = {}; + const idSet = new Set(ids); + const unameSet = new Set(usernames); @@ - for (const user of users) { - if (ids.includes(user.id)) { - realNames[user.id] = user.profile.real_name; - } - if (usernames.includes(user.name)) { - realNames[user.name] = user.profile.real_name; - } - } + for (const user of users) { + if (idSet.has(user.id)) realNames[user.id] = user.profile.real_name; + if (unameSet.has(user.name)) realNames[user.name] = user.profile.real_name; + }
476-487
: Potential label bug:resource.user.name
likely undefined.For DMs,
resource.user
is usually a user ID string, not an object. This returnsDirect Messaging with: @undefined
.getChannelLabel(resource) { - if (resource.user) { - return `Direct Messaging with: @${resource.user.name}`; - } + if (resource.user) return `Direct Messaging with: @${resource.user}`; const { is_private: isPrivate, name, } = resource.channel; return `${isPrivate && "Private" || "Public"} channel #${name}`; }
502-511
: Simplify display name fallback for DMs.Avoid redundant
profile.real_name || profile?.real_name
and adddisplay_name
fallback.- return `@${profile.real_name || profile?.real_name}`; + return `@${profile?.real_name || profile?.display_name || "user"}`;
684-705
: Optionally tryconversations.join
before inviting the bot.For public channels, joining is simpler and avoids invite failures; fall back to invite on error.
- for (const channel of channelIds) { + for (const channel of channelIds) { try { - // Note: Trying to add the app to DM or group DM channels results in - // the error: method_not_supported_for_channel_type - await this.inviteToConversation({ - channel, - users: user_id, - }); + // Attempt join (public channels). If it fails, fall back to invite. + await this.sdk({ asBot: true }).conversations.join({ channel }); + } catch (errJoin) { + try { + await this.inviteToConversation({ channel, users: user_id }); + } catch (error) { + console.log(`Unable to add app to channel ${channel}: ${error}`); + } } catch (error) { console.log(`Unable to add app to channel ${channel}: ${error}`); } }components/slack_v2/actions/common/send-message.mjs (2)
205-206
: Validateblocks
JSON and surface a clear error.- } else if (typeof blocks === "string") { - blocks = JSON.parse(blocks); + } else if (typeof blocks === "string") { + try { + blocks = JSON.parse(blocks); + } catch (e) { + throw new Error(`Invalid JSON in "Blocks": ${e.message}`); + } }
250-253
: Validatepost_at
(ISO-8601, not in the past) before scheduling.if (this.post_at) { - obj.post_at = Math.floor(new Date(this.post_at).getTime() / 1000); + const ts = Date.parse(this.post_at); + if (Number.isNaN(ts)) { + throw new Error('Invalid "Schedule message" datetime. Use ISO 8601 (e.g., 2014-01-01T00:00:00Z).'); + } + if (ts < Date.now()) { + throw new Error('"Schedule message" time cannot be in the past.'); + } + obj.post_at = Math.floor(ts / 1000); return await this.slack.scheduleMessage(obj); }components/slack_v2/actions/common/build-blocks.mjs (3)
160-164
: Defensive: ensurethis.blocks
is an array in “array” mode.Prevents runtime issues if upstream passes a non‑array.
- if (this.passArrayOrConfigure === "array") { - blocks = this.blocks; + if (this.passArrayOrConfigure === "array") { + blocks = Array.isArray(this.blocks) ? this.blocks : []; } else {
73-75
: Avoid deprecatedsubstr
; preferslice
(orcrypto.randomUUID()
for stronger IDs).Small DX fix; optional stronger ID generation shown below.
- action_id: `actionId-${Math.random().toString(36) - .substr(2, 9)}`, // Generates a random action_id + action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_idOptional stronger IDs:
// add at top of file: import { randomUUID } from "crypto"; // and here: action_id: `actionId-${randomUUID()}`,
39-82
: Handle unknown block types explicitly.Fail fast if an unsupported type is passed.
} else if (type === "link_button") { @@ return { type: "actions", elements: buttons, }; } + throw new Error(`Unsupported block type: ${type}`);
components/slack_v2/sources/common/base.mjs (2)
1-1
: Use stable ID generation; avoid deprecatedsubstr
.Prefer
crypto.randomUUID()
(Node 16+) for uniqueness and clarity; fallback toslice
if needed.+import { randomUUID } from "crypto"; @@ - event.pipedream_msg_id = `pd_${Date.now()}_${Math.random() - .toString(36) - .substr(2, 10)}`; + event.pipedream_msg_id = `pd_${Date.now()}_${randomUUID()}`;If
crypto
isn’t available, minimally:- .substr(2, 10)}`; + .slice(2, 12)}`;Also applies to: 172-175
177-181
: Harden$emit
id fallback chain.If
event.channel
is a string,.id
is undefined. Use safe fallbacks.- this.$emit(event, { - id: event.client_msg_id || event.pipedream_msg_id || event.channel.id, + this.$emit(event, { + id: event.client_msg_id + || event.pipedream_msg_id + || (event.channel && (event.channel.id || event.channel)) + || event.user + || String(event.event_ts || Date.now()), summary: this.getSummary(event), ts: event.event_ts || Date.now(), });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (57)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs
(1 hunks)components/slack_v2/actions/approve-workflow/approve-workflow.mjs
(1 hunks)components/slack_v2/actions/archive-channel/archive-channel.mjs
(1 hunks)components/slack_v2/actions/common/build-blocks.mjs
(1 hunks)components/slack_v2/actions/common/send-message.mjs
(1 hunks)components/slack_v2/actions/create-channel/create-channel.mjs
(1 hunks)components/slack_v2/actions/create-reminder/create-reminder.mjs
(1 hunks)components/slack_v2/actions/delete-file/delete-file.mjs
(1 hunks)components/slack_v2/actions/delete-message/delete-message.mjs
(1 hunks)components/slack_v2/actions/find-message/find-message.mjs
(1 hunks)components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs
(1 hunks)components/slack_v2/actions/get-current-user/get-current-user.mjs
(1 hunks)components/slack_v2/actions/get-file/get-file.mjs
(1 hunks)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs
(1 hunks)components/slack_v2/actions/kick-user/kick-user.mjs
(1 hunks)components/slack_v2/actions/list-channels/list-channels.mjs
(1 hunks)components/slack_v2/actions/list-files/list-files.mjs
(1 hunks)components/slack_v2/actions/list-group-members/list-group-members.mjs
(1 hunks)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs
(1 hunks)components/slack_v2/actions/list-replies/list-replies.mjs
(1 hunks)components/slack_v2/actions/list-users/list-users.mjs
(1 hunks)components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs
(1 hunks)components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs
(1 hunks)components/slack_v2/actions/send-large-message/send-large-message.mjs
(1 hunks)components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs
(1 hunks)components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs
(1 hunks)components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs
(1 hunks)components/slack_v2/actions/send-message/send-message.mjs
(1 hunks)components/slack_v2/actions/set-channel-description/set-channel-description.mjs
(1 hunks)components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs
(1 hunks)components/slack_v2/actions/set-status/set-status.mjs
(1 hunks)components/slack_v2/actions/update-group-members/update-group-members.mjs
(1 hunks)components/slack_v2/actions/update-message/update-message.mjs
(1 hunks)components/slack_v2/actions/update-profile/update-profile.mjs
(1 hunks)components/slack_v2/actions/upload-file/upload-file.mjs
(1 hunks)components/slack_v2/common/constants.mjs
(1 hunks)components/slack_v2/package.json
(1 hunks)components/slack_v2/slack_v2.app.mjs
(1 hunks)components/slack_v2/sources/common/base.mjs
(1 hunks)components/slack_v2/sources/common/constants.mjs
(1 hunks)components/slack_v2/sources/new-channel-created/new-channel-created.mjs
(1 hunks)components/slack_v2/sources/new-channel-created/test-event.mjs
(1 hunks)components/slack_v2/sources/new-interaction-event-received/README.md
(1 hunks)components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs
(1 hunks)components/slack_v2/sources/new-interaction-event-received/test-event.mjs
(1 hunks)components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs
(1 hunks)components/slack_v2/sources/new-keyword-mention/test-event.mjs
(1 hunks)components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs
(1 hunks)components/slack_v2/sources/new-message-in-channels/test-event.mjs
(1 hunks)components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs
(1 hunks)components/slack_v2/sources/new-reaction-added/test-event.mjs
(1 hunks)components/slack_v2/sources/new-saved-message/new-saved-message.mjs
(1 hunks)components/slack_v2/sources/new-saved-message/test-event.mjs
(1 hunks)components/slack_v2/sources/new-user-added/new-user-added.mjs
(1 hunks)components/slack_v2/sources/new-user-added/test-event.mjs
(1 hunks)components/slack_v2/sources/new-user-mention/new-user-mention.mjs
(1 hunks)components/slack_v2/sources/new-user-mention/test-event.mjs
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- components/slack_v2/sources/new-keyword-mention/test-event.mjs
- components/slack_v2/sources/new-user-mention/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (26)
components/slack_v2/actions/list-channels/list-channels.mjs (4)
components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
params
(45-49)page
(50-50)nextCursor
(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params
(43-46)page
(47-47)nextCursor
(50-52)components/slack_v2/actions/list-replies/list-replies.mjs (3)
params
(51-55)page
(56-56)nextCursor
(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
params
(38-41)page
(42-42)nextCursor
(45-47)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (3)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
response
(32-34)components/slack_v2/actions/create-channel/create-channel.mjs (1)
response
(33-36)components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
response
(30-33)
components/slack_v2/actions/list-files/list-files.mjs (5)
components/slack_v2/actions/list-channels/list-channels.mjs (1)
params
(31-33)components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
params
(45-49)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)
params
(43-46)components/slack_v2/actions/list-replies/list-replies.mjs (1)
params
(51-55)components/slack_v2/actions/list-users/list-users.mjs (1)
params
(38-41)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
response
(37-41)
components/slack_v2/actions/list-replies/list-replies.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params
(31-33)page
(34-34)nextCursor
(37-39)components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
params
(45-49)page
(50-50)nextCursor
(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params
(43-46)page
(47-47)nextCursor
(50-52)components/slack_v2/actions/list-users/list-users.mjs (3)
params
(38-41)page
(42-42)nextCursor
(45-47)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (2)
msgType
(61-66)emitEvent
(86-86)
components/slack_v2/actions/kick-user/kick-user.mjs (2)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
response
(32-34)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
response
(31-34)
components/slack_v2/actions/list-users/list-users.mjs (2)
components/slack_v2/slack_v2.app.mjs (4)
users
(517-519)users
(632-640)users
(656-664)page
(293-298)components/slack_v2/actions/list-channels/list-channels.mjs (3)
params
(31-33)page
(34-34)nextCursor
(37-39)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response
(40-44)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (2)
resp
(254-254)resp
(255-257)
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)
response
(30-33)
components/slack_v2/slack_v2.app.mjs (13)
components/slack_v2/actions/common/send-message.mjs (3)
channelId
(190-190)resp
(254-254)resp
(255-257)components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
members
(44-44)page
(50-50)nextCursor
(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
usernames
(59-59)page
(47-47)nextCursor
(50-52)components/slack_v2/actions/list-channels/list-channels.mjs (2)
page
(34-34)nextCursor
(37-39)components/slack_v2/actions/list-replies/list-replies.mjs (2)
page
(56-56)nextCursor
(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
page
(42-42)users
(37-37)nextCursor
(45-47)components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response
(40-44)components/slack_v2/actions/delete-file/delete-file.mjs (1)
response
(33-35)components/slack_v2/actions/delete-message/delete-message.mjs (1)
response
(37-41)components/slack_v2/actions/get-file/get-file.mjs (1)
response
(53-55)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
response
(31-34)components/slack_v2/actions/kick-user/kick-user.mjs (1)
response
(42-45)components/slack_v2/actions/send-large-message/send-large-message.mjs (2)
response
(81-81)response
(88-90)
components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)
components/slack_v2/actions/create-channel/create-channel.mjs (1)
name
(31-31)
components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params
(31-33)page
(34-34)nextCursor
(37-39)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params
(43-46)page
(47-47)nextCursor
(50-52)components/slack_v2/actions/list-users/list-users.mjs (4)
params
(38-41)page
(42-42)nextCursor
(45-47)users
(37-37)components/slack_v2/slack_v2.app.mjs (4)
page
(293-298)users
(517-519)users
(632-640)users
(656-664)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
components/slack_v2/actions/kick-user/kick-user.mjs (1)
response
(42-45)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
components/slack_v2/actions/get-file/get-file.mjs (1)
response
(53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params
(31-33)page
(34-34)nextCursor
(37-39)components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
params
(45-49)page
(50-50)nextCursor
(53-55)members
(44-44)components/slack_v2/actions/list-replies/list-replies.mjs (3)
params
(51-55)page
(56-56)nextCursor
(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
params
(38-41)page
(42-42)nextCursor
(45-47)
components/slack_v2/actions/common/send-message.mjs (4)
components/slack_v2/actions/common/build-blocks.mjs (2)
props
(86-86)blocks
(161-161)components/slack_v2/actions/send-large-message/send-large-message.mjs (4)
sentViaPipedreamText
(45-45)metadataEventPayload
(49-49)obj
(66-79)channelName
(91-91)components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
resp
(43-45)components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
resp
(70-72)
components/slack_v2/actions/common/build-blocks.mjs (2)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
blocks
(75-81)components/slack_v2/actions/common/send-message.mjs (1)
blocks
(198-198)
components/slack_v2/sources/common/base.mjs (1)
components/slack_v2/sources/common/constants.mjs (4)
NAME_CACHE_TIMEOUT
(50-50)NAME_CACHE_TIMEOUT
(50-50)NAME_CACHE_MAX_SIZE
(49-49)NAME_CACHE_MAX_SIZE
(49-49)
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (2)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
resp
(43-45)components/slack_v2/actions/common/send-message.mjs (2)
resp
(254-254)resp
(255-257)
components/slack_v2/actions/send-large-message/send-large-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (4)
sentViaPipedreamText
(209-209)metadataEventPayload
(213-213)obj
(231-248)channelName
(258-258)
components/slack_v2/actions/find-message/find-message.mjs (1)
components/slack_v2/slack_v2.app.mjs (4)
cursor
(187-196)cursor
(240-247)cursor
(629-629)cursor
(653-653)
components/slack_v2/actions/update-message/update-message.mjs (2)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response
(40-44)components/slack_v2/actions/delete-message/delete-message.mjs (1)
response
(37-41)
components/slack_v2/actions/get-file/get-file.mjs (1)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
response
(33-35)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (1)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (2)
msgType
(68-74)emitEvent
(101-101)
🪛 Gitleaks (8.28.0)
components/slack_v2/sources/new-interaction-event-received/test-event.mjs
[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
components/slack_v2/sources/new-interaction-event-received/README.md
[style] ~11-~11: Consider using just “reply”.
Context: ...k with other APIs or services, and then reply back to the original message. # Getting Sta...
(RETURN_BACK)
[style] ~85-~85: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hat this source is configured with. 1. Make sure that the message was sent via Pipe...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Publish TypeScript components
- GitHub Check: Lint Code Base
- GitHub Check: pnpm publish
- GitHub Check: Verify TypeScript components
🔇 Additional comments (24)
components/slack_v2/sources/new-user-added/test-event.mjs (1)
1-48
: Fixture looks sanitized and complete.No functional issues found. Safe defaults (empty strings) avoid PII exposure.
components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (1)
15-23
: Confirmconversation
prop supports multiple selection.You set
type: "string[]"
but reuse a singleconversation
propDefinition. Ensure the underlying definition returns an array and UI supports multi-select; otherwise switch to a multi-select propDefinition or wrap withoptions()
to enable multiple values.components/slack_v2/actions/create-channel/create-channel.mjs (1)
29-39
: LGTM! Clean channel name normalization and creation flow.The channel name normalization (replacing whitespace with hyphens and lowercasing) correctly aligns with Slack's channel naming requirements. The API call structure and success summary are appropriate.
components/slack_v2/actions/approve-workflow/approve-workflow.mjs (1)
47-91
: LGTM! Well-structured workflow approval with proper flow suspension.The implementation correctly:
- Suspends the flow to obtain resume/cancel URLs
- Posts a formatted Slack message with interactive buttons
- Links the buttons to the appropriate flow control endpoints
- Handles the special "Channels" value to map to both PUBLIC and PRIVATE channel types
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)
29-36
: LGTM!The implementation correctly maps the UI concept of "channel description" to Slack's API terminology of "purpose". The parameter mapping and API call are consistent with the Slack documentation.
components/slack_v2/sources/new-channel-created/test-event.mjs (1)
1-44
: LGTM!The test fixture provides a comprehensive static event payload that matches the Slack
channel_created
event structure, including all relevant channel properties and metadata.components/slack_v2/sources/new-saved-message/test-event.mjs (1)
1-37
: LGTM!The test fixture correctly represents a Slack
star_added
event with appropriate nested structure for the item and message properties, including the blocks array for rich text formatting.components/slack_v2/actions/list-replies/list-replies.mjs (1)
58-65
: The original code is correct. No changes needed.The Slack API
conversations.replies
endpoint returnsresponse_metadata
containingnext_cursor
as a standard part of the response structure for cursor-based pagination. The destructuring on line 60 is safe because the Slack API guarantees this field exists.The pattern used in list-replies is consistent across all list actions in the codebase (list-channels, list-group-members, list-members-in-channel, list-users), and none include defensive handling. This systematic consistency reflects the proper reliance on the Slack API contract.
Likely an incorrect or invalid review comment.
components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
52-59
: Verify response_metadata structure for usergroups.users.list API call.The web search results are conflicting on whether
usergroups.users.list
includesresponse_metadata
in its response. One source indicates the response contains only{"ok": true, "users": [...]}
withoutresponse_metadata
, which would cause a destructuring error on line 54. However, pagination documentation referencesresponse_metadata.next_cursor
for this endpoint.The pattern is consistent across all list actions in the codebase, suggesting it may work in practice, but the API documentation ambiguity warrants verification—especially since
find-message.mjs
uses defensive optional chaining forresponse_metadata
, indicating some endpoints handle this differently.Recommended actions:
- Test this action against the actual Slack API to confirm the response structure
- If
response_metadata
is missing, add defensive handling:response_metadata: { next_cursor: nextCursor } = {}
- Check Slack API changelog or test against a real workspace to resolve documentation ambiguity
components/slack_v2/actions/get-current-user/get-current-user.mjs (1)
25-31
: ****The
usersInfo
method does exist in the v2 wrapper (defined at line 865 ofslack_v2.app.mjs
). The review comment's assumption that it may not exist is unfounded. The codebase shows that while some methods follow agetXxx
pattern (e.g.,getUserProfile
,getTeamInfo
), others likeusersInfo
andauthTest
use different naming conventions—all valid in the v2 wrapper.Likely an incorrect or invalid review comment.
components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs (1)
16-44
: Review comment is incorrect — props merge order concern is unfoundedThe common.props object does not contain
conversation
,text
, ormrkdwn
properties. It only exports:slack
,as_user
,addToChannel
,post_at
,include_sent_via_pipedream_flag
,customizeBotSettings
,username
,icon_emoji
,icon_url
,replyToThread
,thread_ts
,thread_broadcast
,addMessageMetadata
,metadata_event_type
,metadata_event_payload
,configureUnfurlSettings
,unfurl_links
,unfurl_media
.Since these keys don't exist in
common.props
, spreading...common.props
at the end cannot override the customconversation
,text
, ormrkdwn
props with their PUBLIC/PRIVATE channel restrictions. The current code structure is correct and doesn't require refactoring.Likely an incorrect or invalid review comment.
components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs (1)
16-53
: Prop name is correct; no changes needed.The
thread_broadcast
prop is correct. Insend-message.mjs
, it's intentionally mapped to the Slack API fieldreply_broadcast
in the run method (reply_broadcast: this.thread_broadcast
). The review comment's concern about prop naming is factually incorrect.Regarding props override order: While moving
...common.props
to the beginning would be more conventional, the current structure works correctly because explicit props after the spread override the spread defaults via JavaScript object semantics. This is a style suggestion only, not a critical issue.Likely an incorrect or invalid review comment.
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
1-37
: LGTM!The action follows the standard Slack v2 pattern with proper prop definitions and a clean implementation. The API call and summary export are correctly structured.
components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)
1-32
: LGTM!The event source follows the standard Slack v2 pattern with proper extension of the common base, correct event name configuration, and a clear summary method.
components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs (1)
1-85
: LGTM!The action properly handles both conversation selection and user list scenarios with appropriate validation. The ConfigurationError for missing inputs and the openConversation flow are well-implemented.
components/slack_v2/actions/list-channels/list-channels.mjs (1)
29-51
: LGTM!The pagination logic correctly accumulates channels using cursor-based pagination and respects the page limit. The implementation is consistent with other list actions in the codebase.
components/slack_v2/actions/send-message/send-message.mjs (1)
1-56
: LGTM!The action properly implements dynamic channel type filtering based on user selection. The conditional logic for constraining conversation types is clean and correct.
components/slack_v2/actions/find-message/find-message.mjs (1)
57-60
: Verify sort_dir parameter support and consider exposing channel typesBased on Slack's documentation, the assistant.search.context API supports channel_types as a comma-separated list of public_channel, private_channel, mpim, and im. However, your code hardcodes only
"public_channel,private_channel"
, which excludes direct messages and multi-party IMs that the API supports.More critically, sort_dir is documented for Slack's classic search APIs but not explicitly documented for assistant.search.context. Your code passes
sort_dir: this.sortDirection
to the endpoint—verify that this parameter is actually supported by the assistant.search.context endpoint, as it may be silently ignored or cause unexpected behavior.Recommendations:
- Expose
channelTypes
as a configurable prop (defaulting to the current value to preserve behavior).- Test or confirm with Slack that
sort_dir
is supported byassistant.search.context
before relying on it in production.components/slack_v2/actions/get-file/get-file.mjs (1)
46-58
: LGTM — action is minimal and correct.Good use of
maybeAddAppToChannels
and clear summary. No blocking issues.components/slack_v2/actions/delete-message/delete-message.mjs (1)
28-34
: Fix description text only;as_user
parameter is valid and supported by Slack API.The
as_user
parameter is supported by Slack's chat.delete method (as of October 20, 2025). It's an optional boolean that, when true, deletes the message as the authed user. The only issue is the description text incorrectly says "update" instead of "delete".Apply this fix:
as_user: { propDefinition: [ slack, "as_user", ], - description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.", + description: "Pass true to delete the message as the authed user. Bot users in this context are considered authed users.", },Also applies to: 37-41
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
33-41
: No actionable issues found. The code is correct.The
conversations
prop contains an array of channel IDs (confirmed by the prop definition type"string[]"
and description "Select one or more channels to monitor for new messages"). TheeventNames()
method correctly returns either:
- The selected channel IDs when channels are chosen, or
["message"]
as the fallback event type when no channels are selectedBoth are valid values per the Slack apphook contract. This pattern is consistently applied across related sources (new-message-in-channels, new-keyword-mention).
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
21-27
: Review comment is incorrect — ignore this suggestion.Slack's
conversations.archive
API does support archiving IM and MPIM conversations when those conversation types are supported and the token has corresponding scopes (im:write and mpim:write). The current code allowing MPIM is correct and will not error at runtime with appropriate token permissions. Do not apply the suggested diff.Likely an incorrect or invalid review comment.
components/slack_v2/sources/common/base.mjs (1)
1-4
: All required Slack app client methods are present.Verification confirms that
usersInfo
,conversationsInfo
,getConversationReplies
,getBotInfo
, andgetTeamInfo
all exist in the slack_v2.app.mjs wrapper. No integration breakage risk from missing methods.components/slack_v2/actions/common/build-blocks.mjs (1)
99-102
: Path verification successful; brittleness concern remains valid.The path
common.props.slack.propDefinitions.blocks
exists and exposes the three accessed properties:
type
→"string"
label
→"Blocks"
description
→ populated stringHowever, the underlying concern about brittleness stands: deep nested property access is inherently fragile. If the structure of
slack_v2.app.mjs
orsend-message.mjs
changes during refactoring, these references could break without warning. Consider extracting this to a named constant or utility function to isolate the dependency path.
@@ -0,0 +1,48 @@ | |||
import slack from "../../slack.app.mjs"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect import path.
This file imports from "../../slack.app.mjs"
while all other Slack v2 actions import from "../../slack_v2.app.mjs"
. This inconsistency will likely cause a module resolution error since this is a Slack v2 component.
Apply this diff:
-import slack from "../../slack.app.mjs";
+import slack from "../../slack_v2.app.mjs";
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
import slack from "../../slack.app.mjs"; | |
import slack from "../../slack_v2.app.mjs"; |
🤖 Prompt for AI Agents
In components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs around
line 1, the import path uses "../../slack.app.mjs" which is inconsistent with
other Slack v2 actions; update the import to point to "../../slack_v2.app.mjs"
so the module resolves correctly and matches the v2 component convention.
const buttons = Object.keys(text).map((buttonText) => ({ | ||
type: "button", | ||
text: { | ||
type: "plain_text", | ||
text: buttonText, | ||
emoji: true, | ||
}, | ||
url: text[buttonText], // Access the URL using buttonText as the key | ||
action_id: `actionId-${Math.random().toString(36) | ||
.substr(2, 9)}`, // Generates a random action_id | ||
})); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate link_button
input shape and guard keys.
Object.keys(text)
will throw if text
isn’t a plain object; also clarify error early.
- } else if (type === "link_button") {
- const buttons = Object.keys(text).map((buttonText) => ({
+ } else if (type === "link_button") {
+ if (!text || typeof text !== "object" || Array.isArray(text)) {
+ throw new Error("Link Button expects an object mapping button text → URL");
+ }
+ const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
@@
- action_id: `actionId-${Math.random().toString(36)
- .substr(2, 9)}`, // Generates a random action_id
+ action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id
}));
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const buttons = Object.keys(text).map((buttonText) => ({ | |
type: "button", | |
text: { | |
type: "plain_text", | |
text: buttonText, | |
emoji: true, | |
}, | |
url: text[buttonText], // Access the URL using buttonText as the key | |
action_id: `actionId-${Math.random().toString(36) | |
.substr(2, 9)}`, // Generates a random action_id | |
})); | |
if (!text || typeof text !== "object" || Array.isArray(text)) { | |
throw new Error("Link Button expects an object mapping button text → URL"); | |
} | |
const buttons = Object.keys(text).map((buttonText) => ({ | |
type: "button", | |
text: { | |
type: "plain_text", | |
text: buttonText, | |
emoji: true, | |
}, | |
url: text[buttonText], // Access the URL using buttonText as the key | |
action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id | |
})); |
🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 65 to 76,
the code uses Object.keys(text) without validating that text is a non-null plain
object which will throw for non-objects; update to first guard the input (if
typeof text !== "object" || text === null || Array.isArray(text) then throw a
clear TypeError or return an empty array), then iterate using
Object.entries(text) and filter to only own string key/value pairs (e.g. filter
([k,v]) => typeof k === "string" && typeof v === "string") before mapping to
buttons, and keep generating action_id safely (use slice instead of substr if
desired).
if (this.passArrayOrConfigure == "array") { | ||
props.blocks = { | ||
type: common.props.slack.propDefinitions.blocks.type, | ||
label: common.props.slack.propDefinitions.blocks.label, | ||
description: common.props.slack.propDefinitions.blocks.description, | ||
}; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use strict equality for mode checks.
Avoid coercion. Switch to ===
in both places.
- if (this.passArrayOrConfigure == "array") {
+ if (this.passArrayOrConfigure === "array") {
props.blocks = {
@@
- if (this.passArrayOrConfigure == "array") {
- blocks = this.blocks;
+ if (this.passArrayOrConfigure === "array") {
+ blocks = this.blocks;
} else {
Also applies to: 160-164
🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 97-103 and
also at 160-164, the code uses loose equality (==) for mode checks which can
cause unintended type coercion; change those comparisons to strict equality
(===) in both locations so the checks compare both value and type (e.g., replace
`== "array"` with `=== "array"` and the corresponding else-if/other check
similarly), run tests/lint to ensure no further occurrences remain.
async run({ $ }) { | ||
const response = await this.slack.addReminders({ | ||
text: this.text, | ||
team_id: this.team_id, | ||
time: this.timestamp, | ||
user: this.user, | ||
}); | ||
$.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`); | ||
return response; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add defensive null check before accessing nested property.
The summary export directly accesses response.reminder.id
without verifying that response
or response.reminder
exists. If the Slack API returns an unexpected structure or fails gracefully, this will throw a TypeError
.
Consider adding a check or optional chaining:
- $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`);
+ $.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async run({ $ }) { | |
const response = await this.slack.addReminders({ | |
text: this.text, | |
team_id: this.team_id, | |
time: this.timestamp, | |
user: this.user, | |
}); | |
$.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`); | |
return response; | |
}, | |
async run({ $ }) { | |
const response = await this.slack.addReminders({ | |
text: this.text, | |
team_id: this.team_id, | |
time: this.timestamp, | |
user: this.user, | |
}); | |
$.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`); | |
return response; | |
}, |
🤖 Prompt for AI Agents
In components/slack_v2/actions/create-reminder/create-reminder.mjs around lines
42 to 51, the code directly accesses response.reminder.id which can throw if
response or response.reminder is null/undefined; add a defensive null check or
use optional chaining to safely read the id (e.g., const id =
response?.reminder?.id) and export a safe summary using a fallback message when
id is missing, ensuring the function still returns the original response.
const response = await this.slack.deleteFiles({ | ||
file: this.file, | ||
}); | ||
$.export("$summary", `Successfully deleted file with ID ${this.file}`); | ||
return response; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Add response.ok guard and confirm API method name
Check the Slack API response and fail fast on errors. Also confirm deleteFiles
matches the v2 wrapper.
Apply:
const response = await this.slack.deleteFiles({
file: this.file,
});
- $.export("$summary", `Successfully deleted file with ID ${this.file}`);
- return response;
+ if (!response?.ok) {
+ throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+ }
+ $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+ return response;
Verify method presence:
🏁 Script executed:
#!/bin/bash
fd -a 'slack_v2.app.mjs' components | xargs -I{} rg -n 'deleteFiles|files\.delete' {}
Length of output: 70
🏁 Script executed:
#!/bin/bash
# Check how other methods/actions in the codebase handle response.ok or error handling
rg -n 'response\.ok|throw new Error' components/slack_v2/actions --type=mjs -C 2 | head -100
Length of output: 27
🏁 Script executed:
#!/bin/bash
# Look at the complete delete-file.mjs file to understand current structure
cat components/slack_v2/actions/delete-file/delete-file.mjs
Length of output: 837
🏁 Script executed:
#!/bin/bash
# Check the slack_v2 wrapper to see how the deleteFiles method implementation handles responses
rg -A 10 'deleteFiles\(args' components/slack_v2/slack_v2.app.mjs
Length of output: 247
🏁 Script executed:
#!/bin/bash
# Find makeRequest implementation
rg -A 15 'makeRequest\(' components/slack_v2/slack_v2.app.mjs | head -50
Length of output: 1497
🏁 Script executed:
#!/bin/bash
# Check how other destructive actions (create, delete, update) handle responses
find components/slack_v2/actions -name "*.mjs" | xargs grep -l "response\.ok\|throw new Error" | head -10
Length of output: 313
🏁 Script executed:
#!/bin/bash
# Look at a similar destructive action to see pattern
find components/slack_v2/actions -name "*delete*" -o -name "*remove*" | head -5 | xargs cat
Length of output: 2021
🏁 Script executed:
#!/bin/bash
# Check response.ok pattern in upload-file action (similar destructive operation)
cat components/slack_v2/actions/upload-file/upload-file.mjs
Length of output: 2575
🏁 Script executed:
#!/bin/bash
# Check response.ok pattern in send-message action
cat components/slack_v2/actions/common/send-message.mjs
Length of output: 6738
Add response.ok guard and verify API method exists
The deleteFiles
method correctly maps to Slack's files.delete
API and is properly defined in the v2 wrapper. However, the action should verify the API response before exporting the summary. This aligns with error-handling patterns used in similar destructive operations like upload-file.mjs
.
Apply:
const response = await this.slack.deleteFiles({
file: this.file,
});
- $.export("$summary", `Successfully deleted file with ID ${this.file}`);
- return response;
+ if (!response?.ok) {
+ throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+ }
+ $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+ return response;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const response = await this.slack.deleteFiles({ | |
file: this.file, | |
}); | |
$.export("$summary", `Successfully deleted file with ID ${this.file}`); | |
return response; | |
}, | |
const response = await this.slack.deleteFiles({ | |
file: this.file, | |
}); | |
if (!response?.ok) { | |
throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`); | |
} | |
$.export("$summary", `Successfully deleted file with ID ${this.file}`); | |
return response; | |
}, |
🤖 Prompt for AI Agents
In components/slack_v2/actions/delete-file/delete-file.mjs around lines 33 to
38, the action currently calls this.slack.deleteFiles and unconditionally
exports a success summary; add a guard to (1) verify the API method exists
(throw a clear error if typeof this.slack.deleteFiles !== 'function') before
calling, (2) after awaiting the call check response.ok and if false throw an
Error including response.error or the full response for debugging, and (3) only
export the success summary and return the response when response.ok is true.
const itemUserResponse = await this.slack.usersInfo({ | ||
user: event.user, | ||
}); | ||
|
||
event.userInfo = userResponse.user; | ||
event.itemUserInfo = itemUserResponse.user; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Fetches the same user twice; should use event.item_user
for message author.
This populates itemUserInfo
with the reactor instead of the message author.
- const itemUserResponse = await this.slack.usersInfo({
- user: event.user,
- });
+ const itemUserResponse = await this.slack.usersInfo({
+ user: event.item_user,
+ });
@@
- event.itemUserInfo = itemUserResponse.user;
+ event.itemUserInfo = itemUserResponse.user;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const itemUserResponse = await this.slack.usersInfo({ | |
user: event.user, | |
}); | |
event.userInfo = userResponse.user; | |
event.itemUserInfo = itemUserResponse.user; | |
} | |
const itemUserResponse = await this.slack.usersInfo({ | |
user: event.item_user, | |
}); | |
event.userInfo = userResponse.user; | |
event.itemUserInfo = itemUserResponse.user; |
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs around
lines 92 to 98, the code calls usersInfo twice for the same user (reactor) and
assigns the wrong user to event.itemUserInfo; change the second fetch to use
event.item_user (the message author) instead of event.user, and assign the
response to event.itemUserInfo (keep event.userInfo as the reactor from the
first fetch); ensure you call await this.slack.usersInfo({ user: event.item_user
}) and set event.itemUserInfo = <that response>.user.
"email": "[email protected]", | ||
"first_name": "Test", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use example domains for sample emails.
Replace real-looking emails with reserved example domains to avoid accidental delivery or PII.
- "email": "[email protected]",
+ "email": "[email protected]",
@@
- "email": "[email protected]",
+ "email": "[email protected]",
Also applies to: 78-79
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-reaction-added/test-event.mjs around lines
36-37 (and also lines 78-79), the sample email values use a real-looking
address; replace them with reserved example domains like [email protected] or
[email protected] (or [email protected]) to avoid accidental delivery or PII
exposure, updating both occurrences accordingly.
key: "slack_v2-new-saved-message", | ||
name: "New Saved Message (Instant)", | ||
version: "0.0.7", | ||
description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", | ||
type: "source", | ||
dedupe: "unique", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name/event mismatch (“Saved” vs star_added
)
Source is named “Saved Message” but listens to star_added
(stars). To avoid user confusion, either rename to “New Star Added (Instant)” or update implementation/description to reflect “stars” terminology clearly. Keep the deprecation warning.
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-saved-message/new-saved-message.mjs around
lines 6 to 11, the source is named and described as “Saved Message” but the
event listened for is the Slack star_added event; update naming/description to
match stars or change the event to reflect saved/bookmarked messages.
Specifically, either rename key/name to something like "slack_v2-new-star-added"
and the display name to "New Star Added (Instant)" and update description text
to reference "star" events (keeping the existing deprecation warning), or if the
intent is to track saved/bookmarked messages, change the implementation to
subscribe to the correct Slack event for saved messages and update the
description accordingly.
slackApphook: { | ||
type: "$.interface.apphook", | ||
appProp: "slack", | ||
async eventNames() { | ||
return [ | ||
"star_added", | ||
]; | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Confirm star_added
viability and document shutdown fallback
Slack has been deprecating “stars”. Please confirm star_added
is still emitted for workspaces and note expected EOL. If deprecated for most tenants, consider adding a “Deprecated” badge/annotation in the component metadata and linking to a migration alternative, if any.
🌐 Web query:
Is Slack’s Events API `star_added` event deprecated or removed? What’s the current recommendation for “saved messages”?
💡 Result:
Short answer: The star (saved) events have been effectively retired. Slack’s stars APIs/events (including star_added) are no longer emitted for end‑user “saved” activity and Slack recommends retiring app functionality that depends on the stars APIs. For end users Slack now uses the “Later” / Saved view for saved messages, but there are no public “Later” APIs to replace star events. [1][2]
Sources:
- stars.add method docs (notes that stars can no longer be viewed/interacted with by end users, the star_added event is no longer emitted, and apps should retire stars APIs). [1]
- Slack UI docs describing the Later / Saved for later view (current user-facing replacement for saved messages). [2]
Remove or deprecate the star_added
event—it's no longer emitted by Slack
Slack's stars APIs/events (including star_added) are no longer emitted for end‑user "saved" activity, and Slack recommends retiring app functionality that depends on the stars APIs. The component as written will not receive events. There are no public "Later" APIs to replace star events, so this integration cannot be directly migrated. Consider either removing this event source or marking the component as deprecated with a link to Slack's migration guidance.
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-saved-message/new-saved-message.mjs around
lines 15 to 23, the component is subscribing to the deprecated Slack event
"star_added" which is no longer emitted; either remove "star_added" from the
eventNames array so the source no longer registers for a non‑existent event, or
mark the component as deprecated (add a deprecation flag/comment and update
README/metadata with a short deprecation notice and Slack migration guidance
link) and update any runtime warnings/logs to surface the deprecation to
integrators.
const [ | ||
{ | ||
elements: [ | ||
{ elements = [] } = {}, | ||
] = [], | ||
} = {}, | ||
] = blocks; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emit logic requires user AND keyword; should be OR. Also fragile block parsing and possible TypeError on undefined text.
Current code:
- Scans only the first block/section; misses mentions in later blocks.
- Requires
userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)
which contradicts “username or specific keyword” and can throw iftext
is undefined.
Fix: parse all blocks, add <@USER>
text fallback, make keyword match safe and case-insensitive, and emit on (userMatch OR keywordMatch) OR PD history.
Apply:
@@
- const [
- {
- elements: [
- { elements = [] } = {},
- ] = [],
- } = {},
- ] = blocks;
+ // Collect inner elements from all blocks / sections
+ const blockElements = [];
+ for (const b of (blocks || [])) {
+ for (const el of (b.elements || [])) {
+ if (el && Array.isArray(el.elements)) {
+ blockElements.push(...el.elements);
+ }
+ }
+ }
@@
- let emitEvent = false;
- if (elements) {
- let userMatch = false;
- for (const item of elements) {
- if (item.user_id && item.user_id === this.user) {
- userMatch = true;
- break;
- }
- }
- if (userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)) {
- emitEvent = true;
- }
- }
- if (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE) {
- emitEvent = true;
- }
+ const t = typeof text === "string" ? text : "";
+ // Detect user mention via blocks and fallback to text markup
+ let userMatch = blockElements.some((item) => item && item.user_id === this.user);
+ if (!userMatch && this.user && t.includes(`<@${this.user}>`)) {
+ userMatch = true;
+ }
+ // Keyword match (case-insensitive), guarded against undefined text
+ const kw = this.keyword?.trim();
+ const keywordMatch = kw ? t.toLowerCase().includes(kw.toLowerCase()) : false;
+ // Emit if user OR keyword matches, or always for PD history
+ const emitEvent = userMatch || keywordMatch || (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE);
@@
- if (emitEvent) {
+ if (emitEvent) {
return event;
}
Also applies to: 101-116
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-user-mention/new-user-mention.mjs around
lines 75-82 (also apply same changes to 101-116), the current destructuring only
inspects the first block, can throw if text is undefined, and uses an AND when
it should be an OR for user vs keyword matches; change to iterate over all
blocks and their elements, safely extract text (fallback to the raw "<@USER>"
mention string if element text is missing), normalize text to lowercase before
matching to make keyword checks case-insensitive, compute userMatch =
Boolean(text.match(/<@(\w+)>/)) and keywordMatch = this.keyword &&
text.toLowerCase().includes(this.keyword.toLowerCase()), then emit when
userMatch OR keywordMatch is true (also include PD history emit as before), and
guard all accesses to avoid TypeErrors.
WHY
Summary by CodeRabbit
Release Notes